代理模式是一种特殊设计模式,在调用方与被调用方之间加上一个中间层,在许多场合中有广泛应用。
Why
对于某些业务类,其所有方法都需要执行某些前置或后置操作,比如最简单的日志,或许可以这样做:1
2
3
4
5
6
7
8
9
10
11
12
13public interface IUserDAO {
void save();
}
public class UserDAOImpl implements IUserDAO {
// some logger instance
Logger logger;
public void save() {
logger.debug("UserDAO starts ave()");
System.out.println("user saved");
logger.debug("UserDAO finishes save()");
}
}
但如果对每个业务类都进行这样的操作,一来与业务无关的代码大量重复,二来一旦日志操作发生变化,所有用到日志对象的业务类都需要进行修改。因此,我们需要代理类来完成此类处理。
静态代理
静态代理是一种简单的代理方式,需要修改调用方的业务逻辑,让调用方依赖代理对象,而不是之前的业务对象。
静态代理的实现步骤如下:
- 定义目标类的接口,定义业务相关的方法。
- 目标类实现该接口,实现业务方法。
- 代理类也实现该接口,同时持有目标类对象;在实现业务方法时,调用目标类对象的业务方法。
1 | // 代理对象 |
问题
静态代理虽然简单直观,但对业务是侵入式的,主要问题是:若需要对不同的类或方法进行差异化的代理,则实现较为困难。
动态代理
针对静态代理的问题,动态代理提供了优化。静态代理在编译期就确定了代理逻辑,而动态代理可以在运行时进行代理。
动态代理也称为JDK代理,在这种模式下,代理对象根据接口生成,代理类只能代理接口中定义的方法,因此需要:
- 目标类实现一个接口。
动态代理的步骤如下:
- 定义目标类的接口,定义业务相关的方法。
- 目标类实现该接口,实现业务方法。
- 代理工厂类持有目标类对象。
- 使用
java.lang.reflect.Proxy
的静态方法newProxyInstance
建立针对目标类的代理对象。- 该方法的第三个参数:一个
InvocationHandler
接口实现类的对象,需实现该接口定义的invoke
方法。 - 在该
invoke
方法中利用反射,通过Method
类的invoke
方法调用目标类的业务方法,同时加入代理的逻辑。
- 该方法的第三个参数:一个
1 | import java.lang.reflect.InvocationHandler; |
Going Deep
让我们瞥一眼Proxy
类的newProxyInstance
方法,只看关键代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
{
...
// 从目标类的ClassLoader中获取代理类,或生成代理类并缓存
Class<?> cl = getProxyClass0(loader, intfs);
try {
...
// 获取代理类的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
...
}
// 利用该构造方法生成代理对象
return cons.newInstance(new Object[]{h});
}
...
}
看来主要步骤如下,且最重要的是第一步:
getProxyClass0
方法获取代理类的Class
对象。- 获取代理类的构造方法。
- 利用构造方法生成代理对象。
那么具体产生的是什么样的代理类呢?是一个名为$Proxy0
的类该类声明为public final class $Proxy0 extends Proxy implements <目标接口>
(如有多个代理,则数字增加,可通过ProxyGenerator
类的generateProxyClass
方法手动生成字节码再反编译得到源码,在此不展开),据此可以推断:
- 该类继承自
Proxy
类,因此其父类持有的protected InvocationHandler h
它也能访问到。 - 该类实现了目标接口的所有方法。
不难想象,该类在实现目标接口的方法时,只会做一件事:调用父类InvocationHandler
对象的invoke
方法。至于这个方法具体做些什么,还记得上文中Proxy
类newProxyInstance
方法的第三个参数吗?
子类代理
子类代理即cglib代理,是一种动态代理的补充,在不需目标类实现任何接口的情况下,代理目标类的所有方法。子类代理生成一个目标类的子类对象作为代理,因此需要:
- 目标类提供无参构造方法。
- 目标类不能为final,且不能有final的方法。
首先我们重新定义下没有实现接口的目标类。1
2
3
4
5public class UserDAO {
public void save() {
System.out.println("user saved");
}
}
代理的步骤如下:
- 目标类实现业务方法。
- 代理类持有目标类对象。
- 回调响应或拦截器类实现cglib中
MethodInterceptor
接口的intercept
方法,该拦截器将拦截父类的业务方法并执行具体的操作。- 通过
MethodProxy
类的invokeSuper
方法来调用父类的业务方法。 - 添加代理的逻辑。
- 通过
- 使用cglib的
Enhancer.create()
方法,利用反射生成代理对象。- 指定父类为目标类。
- 指定回调响应对象。
1 | import java.lang.reflect.Method; |
Going Deep
cglib利用字节码框架ASM,读取目标类的字节码,生成一个其子类的字节码,该子类的声明为:1
public class UserService$$EnhancerByCGLIB$$<随机hash> extends UserDAO implements Factory
可见的确是生成了目标类的直接子类,在其中定义了两个重要的方法:1
2
3
4
5
6
7
8
9
10
11final void CGLIB$save$0() {
super.save();
}
public final void save() {
...
if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object)this, ...);
return;
}
super.save();
}
不难看出,该子类中生成了两个方法:
CGLIB$save$0
方法直接调用目标类(父类)的业务方法。save
方法与目标类的业务方法同名,属于重写,当发现回调对象存在时,调用回调对象的intercept
方法,否则仍然直接调用目标类的业务方法。
至于回调对象及其intercept
方法,还记得上文中定义的实现了MethodInterceptor
接口的回调响应类吗?
应用
最典型的应用就是AOP了,通过注解或配置的方式,为目标类方法指定代理,从而动态地将非业务的逻辑代码“切入”真正的业务逻辑,省去了developer的重复劳动。